{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6.4 经典卷积网络模型\n",
    "6.3节介绍了CNN特有的两种网络结构——卷积层和池化层，但是怎么去组合它们更有可能解决真实的图像处理问题呢？这一节将介绍一些经典的CNN结构，通过这些经典的CNN的网络结构可以总结出CNN结构设计的一些模式。\n",
    "\n",
    "### 6.4.1 LeNet-5模型\n",
    "LeNet-5 模型是Yann LeCun教授于1998年在论文*[Gradient based learning applied to document recognition]((https://ieeexplore.ieee.org/document/726791?reload=true&arnumber=726791)* 中提出的，它是第一个成功应用于数字识别问题的卷积神经网络。在MNIST数据集上，LeNet-5模型可以达到大约99.2%的正确率。LeNet-5模型总共有7层，下图展示了LeNet-5模型的架构：\n",
    "<p align='center'>\n",
    "    <img src=../images/图6.15.JPG>\n",
    "    <center>图6.15 LeNet-5模型结构图</center>\n",
    "</p>\n",
    "\n",
    "下面分别介绍每一层的结构：\n",
    "1. **卷积层**，这一层的输入就是原始的图像像素，LeNet-5模型接受的输入层大小为32\\*32\\*1。第一个卷积层过滤器的尺寸为5\\*5，深度为6，不使用全0填充，步长为1。因为没有使用全0填充，所以这一层的输出的尺寸为32-5+1=28，深度为6 。这一个卷积层总共有5\\*5\\*1\\*6+6=156个参数，其中6个为偏置项参数。因为下一层的节点矩阵有28\\*28\\*6=4704个节点，每个节点和5\\*5=25个当前层节点相连，所以本层卷积层总共有4704\\*(25+1)=122304个连接。\n",
    "2. **池化层**，这一层的输入为第一层的输出，是一个28\\*28\\*6的节点矩阵。本层采用的过滤器大小为2\\*2，长和宽的步长均为2，所以本层的输出矩阵大小为14\\*14\\*6。原始的LeNet-5模型中使用的过滤器和6.3.2节介绍的有些细微差别，本书不做具体介绍。\n",
    "3. **卷积层**，本层的输入矩阵大小为14\\*14\\*6，使用的过滤器大小为5\\*5，深度为16。本层不使用全0填充，步长为1。本层的输出矩阵大小为10\\*10\\*16。按照标准的卷积层，本层应该有5\\*5\\*6\\*16+16=2416个参数，10\\*10\\*16\\*(25+1)=41600个连接。\n",
    "4. **池化层**，本层的输入矩阵大小为10\\*10\\*16，采用的过滤器大小为2\\*2，步长为2。本层的输出矩阵大小为5\\*5\\*16。\n",
    "5. **全连接层**，本层的输入矩阵大小为5\\*5\\*16，在LeNet-5模型的论文中将这一层称为卷积层，但是因为过滤器的大小就是5\\*5，所以和全连接层没有区别，在之后的TensorFlow程序实现中也会将这一层看成全连接层。如果将5\\*5\\*16矩阵中的节点拉成一个向量，那么这一层和在第4章中介绍的全连接层输入就一样了。本层的输出节点个数为120，总共有5\\*5\\*16\\*120+120=48120个参数。\n",
    "6. **全连接层**，本层的输入节点个数为120个，输出节点个数为84 个，总共参数为120\\*84+84=10164个。\n",
    "7. **全连接层**，本层的输入节点个数为84个，输出节点个数为10个，总共参数为84\\*10+10=850个。\n",
    "\n",
    "上面介绍了LeNet-5模型每一层结构和设置，下面给出一个TensorFlow的程序来实现一个类似LeNet-5模型的卷积神经网络来解决MNIST数字识别问题。通过TensorFlow训练卷积神经网络的过程和第5章中介绍的训练全连接神经网络是完全一样的。损失函数的计算、反向传播过程的实现都可以复用5.5节中给出的mnist_tain.py程序。唯一的区别在因为CNN的输入层为一个三维矩阵，所以需要调整一下输入数据的格式："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "# 调整输入数据placeholder的格式，输入为一个四维矩阵。\n",
    "x = tf.placeholder(tf.float32, [\n",
    "        BATCH SIZE,                     # 第一维表示一个batch 中样例的个数。\n",
    "        mnist_inference.IMAGE SIZE,     # 第二维和第三维表示图片的尺寸。\n",
    "        mnist_inference.IMAGE SIZE,\n",
    "        mnist_inference.NUM_CHANNELS],  # 第四维表示图片的深度，RBG图片为3。\n",
    "      name='x-input')\n",
    "...\n",
    "# 类似地将输入的训练数据格式调整为一个四维矩阵，并将这个调整后的数据传入sess.run 过程。\n",
    "reshaped_xs = np.reshape(xs, (BATCH SIZE,\n",
    "        mnist inference.IMAGE SIZE,\n",
    "        mnist i口ference.IMAGE SIZE,\n",
    "        mnist inference.NUM CHANNELS))\n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然后在mnist_inference.py中实现类似LeNet-5模型结构的前向传播过程即可。LeNet5_inference和LeNet5_train文件见同目录,下面是LeNet5_eval的代码："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From <ipython-input-1-957dd63617be>:66: read_data_sets (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use alternatives such as official/mnist/dataset.py from tensorflow/models.\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:260: maybe_download (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please write your own downloading logic.\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:262: extract_images (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use tf.data to implement this functionality.\n",
      "Extracting ../../../datasets/MNIST_data\\train-images-idx3-ubyte.gz\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:267: extract_labels (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use tf.data to implement this functionality.\n",
      "Extracting ../../../datasets/MNIST_data\\train-labels-idx1-ubyte.gz\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:110: dense_to_one_hot (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use tf.one_hot on tensors.\n",
      "Extracting ../../../datasets/MNIST_data\\t10k-images-idx3-ubyte.gz\n",
      "Extracting ../../../datasets/MNIST_data\\t10k-labels-idx1-ubyte.gz\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:290: DataSet.__init__ (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use alternatives such as official/mnist/dataset.py from tensorflow/models.\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-2001\n",
      "After 2001 training step(s), validation accuracy = 0.9784\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-4001\n",
      "After 4001 training step(s), validation accuracy = 0.984\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-5001\n",
      "After 5001 training step(s), validation accuracy = 0.9856\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-6001\n",
      "After 6001 training step(s), validation accuracy = 0.9856\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-8001\n",
      "After 8001 training step(s), validation accuracy = 0.9872\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-9001\n",
      "After 9001 training step(s), validation accuracy = 0.9872\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-10001\n",
      "After 10001 training step(s), validation accuracy = 0.9874\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-12001\n",
      "After 12001 training step(s), validation accuracy = 0.9876\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-13001\n",
      "After 13001 training step(s), validation accuracy = 0.988\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-15001\n",
      "After 15001 training step(s), validation accuracy = 0.9884\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-16001\n",
      "After 16001 training step(s), validation accuracy = 0.988\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-17001\n",
      "After 17001 training step(s), validation accuracy = 0.9886\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-19001\n",
      "After 19001 training step(s), validation accuracy = 0.9886\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-19001\n",
      "After 19001 training step(s), validation accuracy = 0.9886\n",
      "INFO:tensorflow:Restoring parameters from MNIST_model/mnist_model-19001\n",
      "After 19001 training step(s), validation accuracy = 0.9886\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-1-957dd63617be>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m     68\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     69\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m'__main__'\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 70\u001b[1;33m     \u001b[0mmain\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32m<ipython-input-1-957dd63617be>\u001b[0m in \u001b[0;36mmain\u001b[1;34m(argv)\u001b[0m\n\u001b[0;32m     65\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mmain\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margv\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     66\u001b[0m     \u001b[0mmnist\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0minput_data\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mread_data_sets\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"../../../datasets/MNIST_data\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mone_hot\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 67\u001b[1;33m     \u001b[0mevaluate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmnist\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     68\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     69\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m'__main__'\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m<ipython-input-1-957dd63617be>\u001b[0m in \u001b[0;36mevaluate\u001b[1;34m(mnist)\u001b[0m\n\u001b[0;32m     60\u001b[0m                     \u001b[1;32mreturn\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     61\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 62\u001b[1;33m             \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mEVAL_INTERVAL_SECS\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     63\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     64\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "import time\n",
    "import tensorflow as tf\n",
    "from tensorflow.examples.tutorials.mnist import input_data\n",
    "import numpy as np\n",
    "\n",
    "# 加载LeNet5_inference.py和LeNet5_train.py中定义的常量和函数\n",
    "import LeNet5_inference\n",
    "import LeNet5_train\n",
    "\n",
    "# 每10秒加载一次最新模型，并在测试数据上测试最新模型的正确率\n",
    "EVAL_INTERVAL_SECS = 10\n",
    "\n",
    "\n",
    "def evaluate(mnist):\n",
    "    with tf.Graph().as_default() as g:\n",
    "        # 1. 定义神经网络输入输出\n",
    "        # 对于MNIST小数据集，预测时不需要batch，但数据格式还是要统一成inference输入。也可以使用mnist.validation.next_batch函数设置batch\n",
    "        x = tf.placeholder(tf.float32, [\n",
    "            mnist.validation.images.shape[0],\n",
    "            LeNet5_inference.IMAGE_SIZE,\n",
    "            LeNet5_inference.IMAGE_SIZE,\n",
    "            LeNet5_inference.NUM_CHANNELS],\n",
    "        name='x-input')\n",
    "        y_ = tf.placeholder(tf.float32, [None, LeNet5_inference.OUTPUT_NODE], name='y-input')\n",
    "        reshaped_feed_x = np.reshape(mnist.validation.images, (\n",
    "                mnist.validation.images.shape[0],\n",
    "                LeNet5_inference.IMAGE_SIZE,\n",
    "                LeNet5_inference.IMAGE_SIZE,\n",
    "                LeNet5_inference.NUM_CHANNELS))\n",
    "        validate_feed = {x: reshaped_feed_x, y_: mnist.validation.labels}\n",
    "\n",
    "        # 2. 计算前向传播结果\n",
    "        y = LeNet5_inference.inference(x, None, None)\n",
    "        \n",
    "        # 3. 计算前向传播的结果的正确率，如果需要对未知的样例进行分了，那么使用tf.argmax(y, 1)就可以得到预测类别\n",
    "        correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))\n",
    "        accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))\n",
    "\n",
    "        # 通过变量重命名的方式来加载模型，这样在前向传播的过程中就不需要调用求滑动平均的\n",
    "        # 函数来获取平均值了。这样就可以完全共用LeNet5_inference.py 中定义的前向传播过程。\n",
    "        variable_averages = tf.train.ExponentialMovingAverage(LeNet5_train.MOVING_AVERAGE_DECAY)\n",
    "        variables_to_restore = variable_averages.variables_to_restore()\n",
    "        saver = tf.train.Saver(variables_to_restore)\n",
    "        \n",
    "        # 每隔EVAL_INTERVAL_SECS秒调用一次计算正确率的过程，并检测训练过程中的正确率变化\n",
    "        while True:\n",
    "            gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.4)    # 给当前session分配固定数量显存\n",
    "            with tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) as sess:\n",
    "                # tf.train.get_checkpoint_state函数会通过checkpoint文件自动找到目录中最新的文件名\n",
    "                ckpt = tf.train.get_checkpoint_state(LeNet5_train.MODEL_SAVE_PATH)\n",
    "                if ckpt and ckpt.model_checkpoint_path:\n",
    "                    # 加载模型\n",
    "                    saver.restore(sess, ckpt.model_checkpoint_path)\n",
    "                    # 通过文件名得到模型保存时迭代的轮数\n",
    "                    global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]\n",
    "                    accuracy_score = sess.run(accuracy, feed_dict=validate_feed)\n",
    "                    print(\"After %s training step(s), validation accuracy = %g\" % (global_step, accuracy_score))\n",
    "                else:\n",
    "                    print('No checkpoint file found')\n",
    "                    return\n",
    "                \n",
    "            time.sleep(EVAL_INTERVAL_SECS)\n",
    "            \n",
    "\n",
    "def main(argv=None):\n",
    "    mnist = input_data.read_data_sets(\"../../../datasets/MNIST_data\", one_hot=True)\n",
    "    evaluate(mnist)\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在MNIST数据集上，上面给出的CNN可以大约达到99.4%的正确率（我这里只跑了20000轮，正确率达到98.86%，可以看到确实还有上升的空间）。相比第5章的98.4%的正确率，CNN可以巨幅提高神经网路在MNIST数据集上的正确率。\n",
    "\n",
    "**图片分类的经典CNN架构**\n",
    "\n",
    "然而一种卷积神经网络架构不能解决所有问题。比如LeNet-5模型就无法很好地处理类似ImageNet这样比较大的图像数据集。那么如何设计卷积神经网络的架构呢？**以下正则表达式公式总结了一些经典的用于图片分类问题的卷积神经网络架构：**\n",
    "<p align='center'>\n",
    "    <img src=../images/CNN架构.JPG>\n",
    "</p>\n",
    "\n",
    "在以上公式中，“卷积层＋”表示一层或者多层卷积层，大部分卷积神经网络中一般最多连续使用三层卷积层。“池化层？”表示没有或者一层池化层。池化层虽然可以起到减少参数防止过拟合问题，但是在部分论文中也发现可以直接通过调整卷积层步长来完成。所以有些卷积神经网络中没有池化层。在多轮卷积层和池化层之后，卷积神经网络在输出之前一般会经过1～2个全连接层。比如LeNet-5模型就可以表示为以下结构:\n",
    "<p align='center'>\n",
    "    <img src=../images/LeNet5架构.JPG>\n",
    "</p>\n",
    "\n",
    "除了LeNet-5模型，2012年ImageNet ILSVRC图像分类挑战的第一名[AlexNet模型](https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf)、2013年ILSVRC第一名[ZFNet模型](https://arxiv.org/abs/1311.2901)以及2014年第二名[VGGNet模型](https://arxiv.org/abs/1409.1556)的架构都满足上面介绍的正则表达式。下表给出了VGGNet论文中作者尝试过的不同卷积神经网络架构，可以看出这些卷积神经网络架构都满足介绍的正则表达式。\n",
    "<p align='center'>\n",
    "    <img src=../images/表6.1.JPG>\n",
    "</p>\n",
    "\n",
    "有了卷积神经网络的架构，那么每一层卷积层或者池化层中的配置需要如何设置呢？表6.1提供了很多线索。在表6.1中，convX-Y表示过滤器的边长为X，深度为Y。比如conv3-64表示过滤器的长和宽都为3，深度为64。从表6.1中可以看出:\n",
    "- VGGNet中的**过滤器边长**一般为3或者1。在LeNet-5模型中，也使用了边长为5的过滤器。一般卷积层的过滤器边长不会超过5，但有些卷积神经网络结构中，处理输入的卷积层中使用了边长为7甚至是11的过滤器。\n",
    "- 在**过滤器的深度**上，大部分卷积神经网络都采用逐层递增的方式。比如在表6.1中可以看到，每经过一次池化层之后，卷积层过滤器的深度会乘以2 。虽然不同的模型会选择使用不同的具体数字，但是逐层递增是比较普遍的模式。\n",
    "- **卷积层的步长**一般为1，但是在有些模型中也会使用2，或者3作为步长。\n",
    "- **池化层的配置**相对简单，用得最多的是最大池化层。池化层的过滤器边长一般为2或者3，步长也一般为2或者3。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.4.2 Inception-v3模型\n",
    "Inception结构时一种和LeNet-5结构完全不同的卷积神经网络结构。**在LeNet-5模型中，不同卷积层通过串联的方式连接在一起，而[Inception-v3模型](https://arxiv.org/abs/1512.00567)中的Inception结构是将不同的卷积层通过并联的方式结合在一起。**在下面的篇幅中将具体介绍Inception结构，并通过TensorFlow-Slim工具来实现Inception-v3模型中的一个模块。\n",
    "\n",
    "在6.4.1中提到了一个卷积层可以使用边长为1、3或者5的过滤器，那么如何在这些边长中选呢？**Inception模块给出了一个方案，那就是同时使用所有不同尺寸的过滤器，然后再将得到的矩阵拼接起来。**下图给出了Inception模块的一个单元结构示意图：\n",
    "<p align='center'>\n",
    "    <img src=../images/图6.16.JPG>\n",
    "</p>\n",
    "\n",
    "上图中不同的矩阵代表了Inception模块中的一条计算路径。虽然过滤器的大小不同，但如果所有的过滤器都使用全0填充且步长为1，那么前向传播得到的结果矩阵的长和宽都与输入矩阵一致。这样经过不同过滤器处理的结果矩阵可以拼接成一个更深的矩阵，并最后将它们在深度这个维度上组合起来。\n",
    "\n",
    "图6.16展示的是Inception模块的核心思想，真正在Inception-v3模型中使用的Inception模块要更加复杂多样，有兴趣可以参考论文，下图给出了Inception-v3模型的结构图：\n",
    "<p align='center'>\n",
    "    <img src=../images/图6.17.JPG>\n",
    "</p>\n",
    "\n",
    "Inception-v3模型总共有46层，由**11个Inception模块**组成。图6.17中方框标注出来的结构就是一个Inception模块。在Inception-v3模型中有**96个卷积层**，如果将6.4.1节中的程序直接搬过来，那么一个卷积层就需要5行代码，于是总共需要480行代码来实现所有的卷积层。这样使得代码的可读性非常差。为了更好地实现类似Inception-v3模型这样的复杂卷积神经网络，在下面将先介绍TensorFlow-Slim工具来更加简洁地实现一个卷积层。以下代码对比了直接使用TensorFlow实现一个卷积层和使用TensorFlow-Slim实现同样结构的神经网络的代码量："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "# 直接使用TensorFlow原始API实现卷积层\n",
    "with tf.variable_scope(scope_name):\n",
    "    weights = tf.get_ variable(\"weight\",...)\n",
    "    biases = tf.get_variable(\"bias\", ...)\n",
    "    conv = tf.nn.conv2d( ... )\n",
    "    relu = tf.nn.relu(tf.nn.bias_add(conv, biases))\n",
    "    \n",
    "# 使用TensorFlow-Slim实现卷积层，在一行中即可实现一个卷积层的前向传播算法。\n",
    "# slim.conv2d函数的有3个参数是必填的：\n",
    "# 第一个参数为输入节点矩阵，\n",
    "# 第二参数是当前卷积层过滤器的深度，\n",
    "# 第三个参数是过滤榕的尺寸。\n",
    "# 可选的参数有过滤器移动的步长、是否使用全0填充、激活函数的选择以及变量的命名空间等。\n",
    "net = slim.conv2d （input, 32, [3 , 3])\n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "因为完整的Inception-v3模型比较长（完整的源码可以参考[tensorflow](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_v3.py)），所以在本书中仅提供Inception-v3模型中结构相对复杂的一个Inception模块的代码实现。以下代码实现了图6.17中红色方框中的Inception模块："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "slim = tf.contrib.slim\n",
    "\n",
    "# slim.arg_scope函数可以用于设置默认的参数取值。其第一个参数是一个函数列表，在这个列表中的函数将使用默认的参数取值，\n",
    "# 比如通过下面的定义，调用slim.conv2d(net, 320, [1, 1])函数时会自动加上stride=1和padding=\"SAME\"的参数，如果在函数调用时\n",
    "# 指定了stride，那么这里的设置就不会再使用。通过这种方式可以进一步减少冗余的代码。\n",
    "with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], stride=1, padding=\"VALID\"):      ######这里怎么又是VALID，存疑#######\n",
    "    ...\n",
    "    # 此处省略了Inception-v3模型中其他的网络结构而直接实现最后红色方框中的Inception模块。\n",
    "    # 假设输入图片经过之前的神经网络的前向传播的结果保存在变量net中。\n",
    "    net = 上一层的输出节点矩阵\n",
    "    \n",
    "    # 为一个Inception模块声明一个统一的变量命名空间\n",
    "    with tf.variable_scope('Mixed_7c'):\n",
    "        # 给Inception模块中的每一条路径声明一个命名空间\n",
    "        with tf.variable_scope('Branch_0'):\n",
    "            branch_0 = slim.conv2d(net, 320, [1, 1], scope='Conv2d_0a_1x1')\n",
    "            \n",
    "        # Inception模块中的第二条路径，这个计算路径上的结构本身也是一个Inception结构\n",
    "        with tf.variable_scope('Branch_1'):\n",
    "            branch_1 = slim.conv2d(net, 384, [1, 1], scope='Conv2d_0a_1x1')\n",
    "            # tf.contact函数可以将多个矩阵拼接起来，其第一个参数指定了拼接的维度，这里3即是深度维度\n",
    "            branch_1 = tf.contact(3, [\n",
    "                # 如图6.17，此处2层卷积层的输入都是branch_1，而不是net\n",
    "                slim.conv2d(branch_1, 384, [1, 3], scope='Conv2d_0b_1x3'),\n",
    "                slim.conv2d(branch_1, 384, [3, 1], scope='Conv2d_0c_3x1')])\n",
    "        \n",
    "        # Inception模块中的第三条路径，这个计算路径也是一个Inception结构\n",
    "        with tf.variable_scope('Branch_2'):\n",
    "            branch_2 = slim.conv2d(net, 448, [1, 1], scope='Conv2d_0a_1x1')\n",
    "            branch_2 = slim.conv2d(branch_2, 384, [3, 3], scope='Conv2d_0b_3x3')\n",
    "            branch_2 = tf.contact(3, [\n",
    "                # 如图6.17，此处2层卷积层的输入都是branch_1，而不是net\n",
    "                slim.conv2d(branch_2, 384, [1, 3], scope='Conv2d_0c_1x3'),\n",
    "                slim.conv2d(branch_2, 384, [3, 1], scope='Conv2d_0d_3x1')])\n",
    "                \n",
    "        # Inception模块中的第四条路径\n",
    "        with tf.variable_scope('Branch_3'):\n",
    "            branch_3 = slim.avg_pool2d(net, [3, 3], scope='AvgPool_0a_3x3')\n",
    "            branch_3 = slim.conv2d(branch_3, 192, [1, 1], scope'Conv2d_0b_1x1')\n",
    "            \n",
    "        # 当前Inception模块的最后输出是由上面4个计算结果拼接得到的\n",
    "        net = tf.contact(3, [branch_0, branch_1, branch_2, branch_3])\n",
    "'''"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
